#include "MemoryManager.h"
#include <stdio.h>

short unsigned int lastSearchIndex = 0;

void init_pagetable(){
	int i;	
	allocatedPages = 0;
	init_masks();

	for(i = 0; i < BS_FRAMES; i++){			//zero out the pagetable
		set_page_address(&pagetable[i], 0);
		set_page_valid(&pagetable[i], 0);
		set_page_alloc(&pagetable[i], 0);
	}
    for(i = 0; i < BS_SIZE; i++){
        backingstore[i] = 0;
    }
	set_page_alloc(&pagetable[0], 1);		//set the first page as allocated
	lastSearchIndex = 0;					//reset last search index
	init_LRU();
}

short unsigned int get_next_free(){

	int index = lastSearchIndex;	//what we're looking at in the PT
	short unsigned int row;				//entry at index in PT

	do{
		if(index < 16383)				//increment counter
			index++;					
		else							//or loop it around
			index = 0;
		
		row = pagetable[index];			//get entry

		if(get_page_alloc(&row) == 0){	//if an empty one found
			lastSearchIndex = index;
			return index;					//return it's index
		}
	}while(index != lastSearchIndex);

	return NO_MORE_MEMORY;	//if no addresses found, return impossible value
}

short unsigned int alloc_pt(int size){
	short unsigned int i;								//loop counter
	short unsigned int pagetable_ID = get_next_free();	//pagetable id
	short unsigned int current_addr = pagetable_ID;		//temp 'pointer' to next entry

	if(allocatedPages + size > BS_FRAMES - 1)					//if there's no room
		return NO_MORE_MEMORY;								//return impossible value
	
	allocatedPages += size;

	
	for(i = 1; i < size; i++){
		set_page_alloc(&pagetable[current_addr], 1);					//mark page as allocated
		set_page_address(&pagetable[current_addr], get_next_free());	//set the pointer to the next address
		current_addr = get_page_address(&pagetable[current_addr]);		//update temp
	}
	set_page_address(&pagetable[current_addr],0);	//mark the end of the page table
	set_page_alloc(&pagetable[current_addr],1);
	return pagetable_ID;
}

void dealloc_pt(int ptid){
	short unsigned int curr_index = ptid;
	short unsigned int next_index;
	while(curr_index != 0){
		next_index = get_page_address(&pagetable[curr_index]);
		set_page_alloc(&pagetable[curr_index], 0);
		set_page_valid(&pagetable[curr_index], 0);
		set_page_address(&pagetable[curr_index], 0);
		allocatedPages--;
		curr_index = next_index;
	}
}

short unsigned int extend_pagetable(int ptid){
	short unsigned int curr_index = ptid;	//start of the page table
	short unsigned int last_index;			//last page in table
	short unsigned int new_index;			//new page to be appended
	if(allocatedPages > BS_FRAMES - 1)
		return NO_MORE_MEMORY;
	
	while(curr_index != 0){				//find the end of the pagetable
		last_index = curr_index;
		curr_index = get_page_address(&pagetable[curr_index]);
	}

	new_index = get_next_free();
	printf("Adding page %hu\n", new_index);
	set_page_address(&pagetable[last_index], new_index);	//update the (no longer) last page in the table
																//with the address of the new one
	set_page_alloc(&pagetable[new_index], 1);	//mark the new page as in use
	set_page_address(&pagetable[new_index], 0); //mark it as the end of a page table

	allocatedPages++;
	return last_index;
}

int get_physical_address(int ptid, int v_address){
	int i;
	short int virtual_frame_number = v_address / FRAME_SIZE;
	short int current_frame = ptid;

	for(i = 0; i < virtual_frame_number; i++)			/*Get the right frame*/
		current_frame = get_page_address(&pagetable[current_frame]);
	
	if(current_frame == 0)
		return -1;
	
	return (current_frame * FRAME_SIZE) + (v_address % 4096);
}

unsigned char read_from_virtual(int ptid, int v_address){
	short unsigned int physical_address = get_physical_address(ptid, v_address);
	access_physical_address(physical_address);
	return backingstore[physical_address]; 
}

void write_to_virtual(int ptid, int v_address, char to_write){
	short unsigned int physical_address = get_physical_address(ptid, v_address);
	access_physical_address(physical_address);
	backingstore[physical_address] = to_write;
}

int access_physical_address(int p_address){
	int i;
	int physical_page = p_address / 4096;
	
	for(i = 0; i < SWAP_SIZE; i++){		/* Look through the swap table to see if the page is in RAM*/
		if(swaptable[i] == physical_page){	
			accessNode(i);
			return;
		}
	}
	nodeFault();						/* If not, do a fault and overwrite whatever was in the tail*/
	swaptable[head->value] = physical_page;
	/*HERE GOES CODE TO ACTUALLY SWAP FRAMES IN AND OUT*/
}

int alloc_m(int ptid, int size){
    int i = 0;                              /* The size of the current block we are building. */
    int block_start_index = 0;              /* The start index of the current block we are building. */
    int access_index = 0;

    if(allocatedPages + (size/FRAME_SIZE) > BS_FRAMES - 1){
        return(-1);
    }
    
    /* Search for a block of the specified size. */
    while(i != size){
        /* If the access_index is out of bounds, there wasn't enough memory left. */
        if(get_physical_address(ptid, access_index) == -1){
            extend_pagetable(ptid);
            continue;
        }
        
        /* If the access index is the block start, make sure it isn't reserved. */
        if(block_start_index == access_index && readBlockHeader(ptid, access_index) == 0){
            access_index += 3;
        }
        
        /* If this is not the block start, just see if the address is available. */
        else if(read_from_virtual(ptid, access_index) == 0 && block_start_index != access_index){
            access_index++;
            i++;
        }
        
        /* If we find the block is not totally available, we need to start over. */
        else{ 
            access_index += readBlockHeader(ptid, access_index) + 3;
            block_start_index = access_index;
            i = 0;
            continue;
        }
    }

    /* If a block of the specified size was found, return it. */
    if(i == size){
        unsigned char sizeAsByteArray[3] = {0};
        int z;

        int_to_chars(size, sizeAsByteArray);
        for(z = 0; z < 3; z++){
            write_to_virtual(ptid, block_start_index+z, sizeAsByteArray[z]);
        }

        return(block_start_index+3);
    }
    /* If a block of the specified size was not found, return NULL. */
    else{
        return(-1);
    }
}


int dealloc_m(int ptid, int index){
    int size, i;
    index -= 3;
    size = readBlockHeader(ptid, index);

    for(i = 0; i < size; i++){
        write_to_virtual(ptid, index + i, 0);
    }

    return(0);
}


int readBlockHeader(int ptid, int index){
    unsigned char byteArray[4] = {0};
    unsigned int integer = 0;
    int i = 0;
    
    byteArray[0] = read_from_virtual(ptid, index);
    byteArray[1] = read_from_virtual(ptid, index+1);
    byteArray[2] = read_from_virtual(ptid, index+2);
    
    integer = (int)byteArray[i];

    /* Build the number from the byte array using black magic. */
    for(i = 1; i < 3; i++){
        int num = (int)byteArray[i];
        int j;
        for(j = 0; j < i; j++){
            num *= 256;
        }
        integer += num;
    }

    return(integer);
}